package org.wevil.util;

import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Paragraph;
import org.apache.poi.hwpf.usermodel.Range;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackagingURIHelper;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComment;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComments;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CommentsDocument;

import javax.xml.namespace.QName;
import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;

import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;


/**
 * word读写工具类
 *
 * @author wanglei
 * @since 2019/5/21
 */
public class WordUtil {

    private static final String wordRegex = ".*(doc[x|m])";

    // ============================ 其他 =======================================================//

    /**
     * 加密word，目前只支持xwpf
     * */
    public static void encryptXWPF(String filePath, String password){
        try{
            XWPFDocument word = WordUtil.get07Word(filePath);
            assert word != null;
            word.enforceReadonlyProtection(password, null);
            word.write(Files.newOutputStream(Paths.get(filePath)));
        }catch (Exception e){
            System.out.println("文件地址或格式有误，加密失败。文件地址：" + filePath);
        }
    }

    // ============================ 读取 =======================================================//

    /**
     * 获取 Microsoft Word (97-2003) 的文件对象
     */
    public static HWPFDocument get9703Word(String filePath) {
        try {
            InputStream is = new FileInputStream(filePath);
            return new HWPFDocument(is);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        return null;
    }

    /**
     * 获取 Microsoft Word (2007+) 的文件对象
     */
    public static XWPFDocument get07Word(String filePath) {
        try {
            InputStream is = new FileInputStream(filePath);
            return new XWPFDocument(is);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        return null;
    }

    /**
     * 获取word文档纯文本内容。<br/>
     * 1. 实现了docx读取表格内容，表格每列用“双逗号”分隔。示例：经费投入(万元),,年  度,,投入金额,,经费来源
     */
    public static List<String> getWordText(String filepath) {
        String filename = StringUtil.getFileExtension(filepath);
        if (Pattern.matches(wordRegex, filename)) {
            return getParagraphTextDocx(filepath);
        } else {
            return getParagraphTextDoc(filepath);
        }
    }

    // ============================ 编辑 =======================================================//

    /**
     * 替换word内的内容
     *
     * @param filePath          word文件路径
     * @param destPath          输出文件目录
     * @param replaceArrayKey   查找文字
     * @param replaceArrayValue 替换文字
     */
    public static void replaceWordContent(String filePath, String destPath, String[] replaceArrayKey, String[] replaceArrayValue) {
        String filename = StringUtil.getFileExtension(filePath);
        if (Pattern.matches(wordRegex, filename)) {
            Map<String, String> map = new HashMap<>();
            for(int i=0; i<replaceArrayKey.length; i++){
                map.put(replaceArrayKey[i], replaceArrayValue[i]);
            }
            searchAndReplace(filePath, destPath, map);
//            replaceDocxContent(filePath, destPath, replaceArrayKey, replaceArrayValue);
        } else {
            replaceDocContent(filePath, destPath, replaceArrayKey, replaceArrayValue);
        }
    }

    /**
     * 替换word内的内容
     * Modification History:
     * Date         Author      Version     Description
     * -----------------------------------------------------------------
     * 2018-1-5        ywl       v1.0.0       create
     * -----------------------------------------------------------------
     * @param srcPath 原路径
     * @param destPath 保存路径
     * @map key为需要替换的 value为替换的内容
     */
    public static void searchAndReplace(String srcPath, String destPath,Map<String, String> map) {
        FileOutputStream outStream = null;
        InputStream is = null;
        try {
            // 远程路径
//            URL url = new URL(srcPath);
//            is = url.openStream();
            // 本地路径
            is = new FileInputStream(new File(srcPath));
            XWPFDocument document = new XWPFDocument(is);
            /**
             * 替换段落中的指定文字
             */
            Iterator<XWPFParagraph> itPara = document.getParagraphsIterator();
            while (itPara.hasNext()) {
                XWPFParagraph paragraph = (XWPFParagraph) itPara.next();
                Set<String> set = map.keySet();
                Iterator<String> iterator = set.iterator();
                while (iterator.hasNext()) {
                    String key = iterator.next();
                    List<XWPFRun> run=paragraph.getRuns();
                    for(int i=0;i<run.size();i++)
                    {
                        if(run.get(i).getText(run.get(i).getTextPosition())!=null &&
                                run.get(i).getText(run.get(i).getTextPosition()).contains(key))
                        {
                            /**
                             * 参数0表示生成的文字是要从哪一个地方开始放置,设置文字从位置0开始
                             * 就可以把原来的文字全部替换掉了
                             */
                            String text = run.get(i).getText(run.get(i).getTextPosition());
                            text = text.replaceAll(key,map.get(key));
                            run.get(i).setText(text,0);
                        }
                    }
                }
            }

            /**
             * 替换表格中的指定文字
             */
            Iterator<XWPFTable> itTable = document.getTablesIterator();
            while (itTable.hasNext()) {
                XWPFTable table = (XWPFTable) itTable.next();
                int count = table.getNumberOfRows();
                for (int i = 0; i < count; i++) {
                    XWPFTableRow row = table.getRow(i);
                    List<XWPFTableCell> cells = row.getTableCells();
                    for (XWPFTableCell cell : cells) {
                        for (Map.Entry<String, String> e : map.entrySet()) {
                            if (cell.getText().equals(e.getKey())) {
                                cell.removeParagraph(0);
                                cell.setText(e.getValue());
                            }
                        }
                    }
                }
            }
            outStream = new FileOutputStream(destPath);
            document.write(outStream);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(null != is){
                    is.close();
                }
                if(null != outStream){
                    outStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 向word中添加批注
     *
     * @param filePath word文件路径
     */
    public static void annotationWordContent(String filePath, String destPath, String[] replaceArrayKey) {
        String filename = StringUtil.getFileExtension(filePath);
        if (Pattern.matches(wordRegex, filename)) {
            annotationDocxContent(filePath, destPath, replaceArrayKey);
        } else {
            //annotationDocContent(filePath, destPath, replaceArrayKey);
        }
    }

    /**
     * 找出word中存在的敏感词 返回一个List 集合
     *
     * @param srcPath 输入word文件
     * @param list    敏感词集合
     * @return
     * @throws IOException
     */
    public static List<String> searchKeyWord(String srcPath, List<String> list) throws Exception {
        List<String> stringList = new ArrayList<String>();
        List<String> contexts = getWordText(srcPath);
        if (contexts == null) {
            return null;
        }
        for (String context : contexts) {
            for (String key : list) {
                if (context.contains(key)) {
                    stringList.add(key);
                }
            }
        }
        return stringList;
    }

    /**
     * 插入表格
     *
     * @param doc    word对象
     * @param key    要插入位置的占位符
     * @param tables 表格数据
     * @param columnWidth 列宽，百分比
     * @author wanglei
     * @since 2020/7/17
     */
    public static void insertTable(XWPFDocument doc, String key, String[][] tables, String[] columnWidth) {
        List<XWPFParagraph> paragraphList = doc.getParagraphs();
        if (paragraphList != null && paragraphList.size() > 0) {
            for (XWPFParagraph paragraph : paragraphList) {
                String text = paragraph.getText();
                if (text.contains(key)) {
                    // 获取游标
                    XmlCursor xmlCursor = paragraph.getCTP().newCursor();
                    // 在指定游标位置插入表格
                    XWPFTable table = doc.insertNewTbl(xmlCursor);
                    // 设置表格宽度
                    table.setWidth("100%");
                    for (int i = 0; i < tables.length; i++) {
                        // 插入行
                        String[] rows = tables[i];
                        XWPFTableRow row;
                        // 如果是第一行，不用创建
                        if (i == 0) {
                            row = table.getRow(i);
                        } else {
                            row = table.insertNewTableRow(i);
                        }
                        for (int j = 0; j < rows.length; j++) {
                            String content = rows[j];
                            XWPFTableCell cell;
                            // 如果是第一行第一列，不用创建
                            if (i == 0 && j == 0) {
                                cell = row.getCell(j);
                            } else {
                                cell = row.createCell();
                            }
                            cell.setWidth(columnWidth[j]);
                            XWPFParagraph paragraph1 = cell.getParagraphs().get(0);
                            paragraph1.setAlignment(ParagraphAlignment.CENTER);
                            XWPFRun run1 = paragraph1.createRun();
                            run1.setFontFamily("仿宋_GB2312");
                            run1.setFontSize(14);
                            run1.setText(content);
                        }
                    }
                    // 删除原本的占位符
                    doc.removeBodyElement(doc.getPosOfParagraph(paragraph));
                    break;
                }

            }
        }
    }

    // ============================ 私有类 =======================================================//

    /**
     * 添加批注在docx中
     *
     * @param filePath        文件输入路径
     * @param destPath        文件输出路径
     * @param replaceArrayKey 需要加批准的关键词
     */
    private static void annotationDocxContent(String filePath, String destPath, String[] replaceArrayKey) {
        XWPFDocument document1 = null;
        try {
            document1 = new XWPFDocument(new FileInputStream(new File(filePath))).getXWPFDocument();
            MyXWPFCommentsDocument myXWPFCommentsDocument = createCommentsDocument(document1);
            CTComments comments = myXWPFCommentsDocument.getComments();
            // 创建绑定ID
            int i = 0;
            List<XWPFParagraph> paragraphList = document1.getParagraphs();
            for (XWPFParagraph paragraph : paragraphList) {
                for (String s : replaceArrayKey) {
                    if (paragraph.getParagraphText().contains(s)) {
                        i++;
                        // 创建批注对象
                        CTComment ctComment = comments.addNewComment();
                        ctComment.setAuthor("Axel Ríchter");
                        ctComment.setInitials("AR");
                        ctComment.setDate(new GregorianCalendar(Locale.US));
                        // 设置批注内的内容
                        ctComment.addNewP().addNewR().addNewT().setStringValue("包含敏感词" + s);
                        // 将上面创建的绑定ID设置进批注对象
                        ctComment.setId(BigInteger.valueOf(i));
                        paragraph.getCTP().addNewR().addNewCommentReference().setId(BigInteger.valueOf(i));
                    }
                }
            }
            document1.write(new FileOutputStream(destPath));
            document1.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 替换docx的内容
     *
     * @param filePath          word文件路径
     * @param replaceArrayKey   需要替换的内容数组
     * @param replaceArrayValue 需要替换为的值数组，如果为null则默认为替换为空字符串
     */
    private static void replaceDocxContent(String filePath, String destPath, String[] replaceArrayKey, String[] replaceArrayValue) {
        try {
            //获取到输入路径的word文件
            InputStream inputStream = new FileInputStream(filePath);
            // 获取document
            XWPFDocument document = new XWPFDocument(inputStream);
            // 替换段落内容
            replaceDocxParagraph(replaceArrayKey, replaceArrayValue, document);
            // 替换表格文字
            replaceDocxTable(replaceArrayKey, replaceArrayValue, document);
            // 写入文件
            FileOutputStream outStream = new FileOutputStream(destPath);
            document.write(outStream);
            // 关闭流
            inputStream.close();
            outStream.close();
            document.close();
        } catch (IOException ioe) {
            System.out.println("替换文件失败：[" + filePath + "]");
            System.out.println(ioe.getMessage());
        }
    }

    /**
     * 替换doc的内容
     *
     * @param filePath word文件路径
     */
    private static void replaceDocContent(String filePath, String destPath, String[] replaceArrayKey, String[] replaceArrayValue) {
        HWPFDocument document;
        try {
            InputStream inputStream = new FileInputStream(filePath);
            document = new HWPFDocument(inputStream);
            Range range = document.getRange();
            for (int i = 0; i < replaceArrayKey.length; i++) {
                if (replaceArrayValue != null) {
                    range.replaceText(replaceArrayKey[i], replaceArrayValue[i]);
                } else {
                    range.replaceText(replaceArrayKey[i], "");
                }
            }
            FileOutputStream outStream;
            outStream = new FileOutputStream(destPath);
            document.write(outStream);
            inputStream.close();
            outStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("替换文件失败：[" + filePath + "]");
            System.out.println(e.getMessage());
        }
    }

    /**
     * 获取03版word文档标题和内容
     */
    private static List<String> getParagraphTextDoc(String path) {
        try {
            InputStream is = new FileInputStream(path);
            HWPFDocument doc = new HWPFDocument(is);
            Range r = doc.getRange();
            List<String> list = new ArrayList<String>();
            for (int i = 0; i < r.numParagraphs(); i++) {
                Paragraph p = r.getParagraph(i);
                String contexts = p.text();
                // 标题+内容
                list.add(contexts);
            }
            is.close();
            return list;
        } catch (IOException ioe) {
            String fileName = StringUtil.getFileNameByPath(path);
            System.out.println("读取[" + fileName + "] 失败：" + ioe.getMessage());
        }
        return null;
    }

    /**
     * 获取2007版.docx word文档内容
     */
    private static List<String> getParagraphTextDocx(String filePath) {
        try {
            InputStream is = new FileInputStream(filePath);
            XWPFDocument doc = new XWPFDocument(is);

            List<String> context = new ArrayList<>();

            // 获取段落数据
            List<XWPFParagraph> paras = doc.getParagraphs();
            for (XWPFParagraph para : paras) {
                String str = "  " + para.getText().replaceAll("\\n", "").replaceAll("\\t", "") + "\n";
                context.add(str);
            }

            // 获取表格数据
            List<XWPFTable> tables = doc.getTables();
            for(XWPFTable table : tables){
                int numberOfRows = table.getNumberOfRows();
                for(int i=0; i<numberOfRows; i++){
                    XWPFTableRow row = table.getRow(i);
                    List<XWPFTableCell> tableCells = row.getTableCells();
                    StringBuilder rowText = new StringBuilder();
                    for(XWPFTableCell cell : tableCells){
                        // 双逗号作为分隔符
                        rowText.append(cell.getText()).append(",,");
                    }
                    rowText = new StringBuilder(rowText.substring(0, rowText.length() - 2));
                    rowText.append("\r\n");
                    context.add(rowText.toString());
                }
            }

            is.close();
            return context;
        } catch (IOException io) {
            System.out.println("获取文件失败：[" + filePath + "]");
            System.out.println(io.getMessage());
        }
        return null;
    }

    /**
     * 替换段落内容
     *
     * @param replaceArrayKey   需要替换的内容数组
     * @param replaceArrayValue 需要替换为的值数组，如果为null则默认为替换为空字符串
     * @param document          poi document对象
     */
    private static void replaceDocxParagraph(String[] replaceArrayKey, String[] replaceArrayValue, XWPFDocument document) {
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            List<XWPFRun> runs = paragraph.getRuns();
            for (XWPFRun run : runs) {
                //替换模板原来位置
                String value = changeValue(run.toString(), replaceArrayKey, replaceArrayValue);
                //分段显示的情况
                String[] values = value.split("\r\n");
                if (values.length > 1) {
                    run.setText(values[0], 0);
                    for (int i = 1; i < values.length; i++) {
                        //存在分段则新建一个run
                        XWPFRun newrun = paragraph.insertNewRun(i);
                        //copy样式
                        newrun.getCTR().setRPr(run.getCTR().getRPr());
                        //换行
                        newrun.addBreak();
                        //缩进
                        newrun.addTab();
                        newrun.setText(values[i]);
                    }
                    break;
                } else {
                    run.setText(value, 0);
                }
            }
        }
    }

    /**
     * 替换Docx的表格
     *
     * @param replaceArrayKey   需要替换的内容数组
     * @param replaceArrayValue 需要替换为的值数组，如果为null则默认为替换为空字符串
     * @param document          poi document对象
     */
    private static void replaceDocxTable(String[] replaceArrayKey, String[] replaceArrayValue, XWPFDocument document) {
        Iterator<XWPFTable> itTable = document.getTablesIterator();
        while (itTable.hasNext()) {
            XWPFTable table = itTable.next();
            int rcount = table.getNumberOfRows();
            for (int i = 0; i < rcount; i++) {
                XWPFTableRow row = table.getRow(i);
                List<XWPFTableCell> cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    String cellTextString = cell.getText();
                    cellTextString = changeValue(cellTextString, replaceArrayKey, replaceArrayValue);
                    cell.removeParagraph(0);
                    cell.setText(cellTextString);
                }
            }
        }
    }

    /**
     * 将一段内容中的关键字替换为其他内容
     *
     * @param str               字符串
     * @param replaceArrayKey   需要替换的内容数组
     * @param replaceArrayValue 需要替换为的值数组，如果为null则默认为替换为空字符串
     */
    private static String changeValue(String str, String[] replaceArrayKey, String[] replaceArrayValue) {
        for (int i = 0; i < replaceArrayKey.length; i++) {
            if (replaceArrayValue != null) {
                str = str.replace(replaceArrayKey[i], replaceArrayValue[i]);
            } else {
                str = str.replace(replaceArrayKey[i], "");
            }
        }
        return str;
    }

    /**
     * 创建word docx的批注
     */
    private static MyXWPFCommentsDocument createCommentsDocument(XWPFDocument document) throws Exception {
        OPCPackage oPCPackage = document.getPackage();
        PackagePartName partName = PackagingURIHelper.createPartName("/word/comments.xml");
        PackagePart part = oPCPackage.createPart(partName, "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml");
        MyXWPFCommentsDocument myXWPFCommentsDocument = new MyXWPFCommentsDocument(part);

        String rId = "rId" + (document.getRelationParts().size() + 1);
        document.addRelation(rId, XWPFRelation.COMMENT, myXWPFCommentsDocument);

        return myXWPFCommentsDocument;
    }

    /**
     * 创建word docx的批注
     */
    private static class MyXWPFCommentsDocument extends POIXMLDocumentPart {

        private CTComments comments;

        private MyXWPFCommentsDocument(PackagePart part) {
            super(part);
            comments = CommentsDocument.Factory.newInstance().addNewComments();
        }

        private CTComments getComments() {
            return comments;
        }

        @Override
        protected void commit() throws IOException {
            XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
            xmlOptions.setSaveSyntheticDocumentElement(new QName(CTComments.type.getName().getNamespaceURI(), "comments"));
            PackagePart part = getPackagePart();
            OutputStream out = part.getOutputStream();
            comments.save(out, xmlOptions);
            out.close();
        }
    }
}

